<!DOCTYPE html>
<!--
Copyright 2019 The Chromium Authors. All rights reserved.
Use of this source code is governed by a BSD-style license that can be
found in the LICENSE file.
-->

<link rel="import" href="/tracing/base/base64.html">
<link rel="import" href="/tracing/extras/chrome/speed_index_utils.html">
<link rel="import" href="/tracing/metrics/system_health/breakdown_tree_helpers.html">
<script src="/jpeg-js-decoder.js"></script>

<script>
'use strict';

tr.exportTo('tr.metrics.sh', function() {
  const timeDurationInMs_smallerIsBetter =
      tr.b.Unit.byName.timeDurationInMs_smallerIsBetter;
  const SpeedIndex = tr.e.chrome.SpeedIndex;

  const LOADING_METRIC_BOUNDARIES = tr.v.HistogramBinBoundaries
      .createLinear(0, 1e3, 20)  // 50ms step to 1s
      .addLinearBins(3e3, 20) // 100ms step to 3s
      .addExponentialBins(20e3, 20);


  const SUMMARY_OPTIONS = {
    avg: true,
    count: false,
    max: true,
    min: true,
    std: true,
    sum: false,
  };

  /**
   * Adds a speed index sample.
   *
   * @param {tr.model.helpers.ChromeBrowserrHelper} browserHelper
   * @param {number[]} samples
   * @param {tr.model.ThreadSlice} navigationStart
   */
  function addSpeedIndexScreenshotsBasedSample(samples, navigationStart,
      loadDuration, browserHelper) {
    const screenshotObjects = browserHelper.process.objects
        .getAllInstancesNamed('Screenshot');
    if (!screenshotObjects) return;
    for (let i = 0; i < screenshotObjects.length; i++) {
      const snapshots = screenshotObjects[i].snapshots;
      const timestampedColorHistograms = [];
      snapshots.map(snapshot => {
        if (snapshot.ts >= navigationStart.start &&
        snapshot.ts < navigationStart.start + loadDuration) {
          timestampedColorHistograms.push({colorHistogram:
              SpeedIndex.createColorHistogram(getPixelData(snapshot.args)),
          ts: snapshot.ts});
        }
      });
      samples.push(
          {value: SpeedIndex.calculateSpeedIndex(timestampedColorHistograms) -
          navigationStart.start});
    }
  }

  /**
   * Extract RGBA pixel values from a base64 JPEG.
   *
   * @param {string} base64JpegImage - base64 jpeg image
   * @return {number[]} - a flat array of rgba values for each pixel
   */
  function getPixelData(base64JpegImage) {
    const binaryString = atob(base64JpegImage);
    const bytes = new DataView(new ArrayBuffer(base64JpegImage.length));
    tr.b.Base64.DecodeToTypedArray(base64JpegImage, bytes);
    const rawImageData = jpegDecode(bytes.buffer, {useTArray: true});
    return rawImageData.data;
  }

  function collectSpeedIndexSamplesFromLoadExpectations(model, chromeHelper) {
    const speedIndexScreenshotsBasedSamples = [];
    for (const expectation of model.userModel.expectations) {
      if (!(expectation instanceof tr.model.um.LoadExpectation)) continue;
      if (tr.e.chrome.CHROME_INTERNAL_URLS.includes(expectation.url)) {
        continue;
      }
      const rendererHelper = chromeHelper.rendererHelpers[
          expectation.renderProcess.pid];
      addSpeedIndexScreenshotsBasedSample(speedIndexScreenshotsBasedSamples,
          expectation.navigationStart, expectation.duration,
          chromeHelper.browserHelper);
    }
    return speedIndexScreenshotsBasedSamples;
  }
  /** This metric is experminetal and should not be used. **/
  function screenshotsBasedSpeedIndexMetric(histograms, model) {
    const speedIndexScreenshotsBasedHistogram = histograms.createHistogram(
        'speedIndexScreenshotsBased', timeDurationInMs_smallerIsBetter, [], {
          binBoundaries: LOADING_METRIC_BOUNDARIES,
          description: 'The average time at which visible parts of the' +
          ' page are displayed.',
          summaryOptions: SUMMARY_OPTIONS,
        });

    const chromeHelper = model.getOrCreateHelper(
        tr.model.helpers.ChromeModelHelper);

    const samples =
        collectSpeedIndexSamplesFromLoadExpectations(model, chromeHelper);
    for (const sample of samples) {
      speedIndexScreenshotsBasedHistogram.addSample(sample.value);
    }
  }

  tr.metrics.MetricRegistry.register(screenshotsBasedSpeedIndexMetric);

  return {
    screenshotsBasedSpeedIndexMetric
  };
});
</script>
